package tests;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.io.IOException;

import org.junit.Before;
import org.junit.Test;

import peerToPeerFacebook.proj.DataManager;
import peerToPeerFacebook.proj.LockManager;
import peerToPeerFacebook.proj.DataManager.TransactionStatus;
import tests.MemoryStorage.StorageCrash;

public class DMTest
{
	protected MemoryStorage storage = null;
	protected LockManager locks = null;
	protected DataManager dm = null;
	
	protected final String testFileExists = "exists";
	protected final String testFileNew = "new";
	protected final String transaction = "foobar";
	protected final String testFileContent = "Hello, this is test data from setUp";

	protected String generateDirtyName(String name)
	{
		return name + ".dirty";
	}
	
	@Before
	public void setUp() throws Exception
	{
		storage = new MemoryStorage();
		storage.writeToFile(testFileExists, testFileContent);
		
		simulateCrash();
	}

	protected void simulateCrash() throws IOException
	{
		locks = new LockManager();
		dm = new DataManager(transaction, storage, locks);
	}

	@Test
	public void testPutAppendCommit() throws IOException
	{
		dm.createFile(testFileNew);
		dm.putContentsToFile(testFileNew, "Hello world 1!\r\n");
		dm.appendContentsToFile(testFileNew, "Hello world 2!\r\n");
		
		// read already exzisting file, to pick the lock
		assertEquals(testFileContent + "\r\n", dm.getFileContents(testFileExists));
		
		// check to make sure that we do not have this file yet
		assertFalse(storage.fileExists(testFileNew));
		
		// check locks
		assertTrue(locks.readOwners(testFileNew).contains(transaction));
		assertTrue(locks.writeOwners(testFileNew).contains(transaction));
		assertTrue(locks.readOwners(testFileExists).contains(transaction));
		assertFalse(locks.writeOwners(testFileExists).contains(transaction));
		
		// Commit time
		dm.preCommit();
		dm.commit();
		
		// now we have the file and the stuff
		assertTrue(storage.fileExists(testFileNew));
		assertFalse(storage.fileExists(generateDirtyName(testFileNew)));
		
		// content check
		String lines[] = storage.readFile(testFileNew);
		assertEquals(2, lines.length);
		assertEquals("Hello world 1!", lines[0]);
		assertEquals("Hello world 2!", lines[1]);
		
		// check locks
		assertFalse(locks.readOwners(testFileNew).contains(transaction));
		assertFalse(locks.writeOwners(testFileNew).contains(transaction));
		assertFalse(locks.readOwners(testFileExists).contains(transaction));
		assertFalse(locks.writeOwners(testFileExists).contains(transaction));
	}

	@Test
	public void testDeleteCommit() throws IOException
	{
		dm.deleteFile(testFileExists);
		
		// make sure file is there
		assertTrue(storage.fileExists(testFileExists));

		// check locks
		assertTrue(locks.readOwners(testFileExists).contains(transaction));
		assertTrue(locks.writeOwners(testFileExists).contains(transaction));

		// commit
		dm.preCommit();
		dm.commit();
		
		// file should be gone
		assertFalse(storage.fileExists(testFileExists));

		// check locks
		assertFalse(locks.readOwners(testFileExists).contains(transaction));
		assertFalse(locks.writeOwners(testFileExists).contains(transaction));
	}
	
	@Test
	public void testAbort() throws IOException
	{
		// Put some stuff into the files
		final String contentAppend = "This should not be visible";
		final String contentWrite = "Hello world!";

		dm.appendContentsToFile(testFileExists, contentAppend);
		
		dm.createFile(testFileNew);
		dm.putContentsToFile(testFileNew, contentWrite);
		
		// chedck the state of the files
		// Old file stuff
		String[] fileData = storage.readFile(testFileExists);
		assertEquals(1, fileData.length);
		assertEquals(testFileContent, fileData[0]);
		
		fileData = storage.readFile(generateDirtyName(testFileExists));
		assertEquals(2, fileData.length);
		assertEquals(testFileContent, fileData[0]);
		assertEquals(contentAppend, fileData[1]);
		
		// New file stuff
		assertFalse(storage.fileExists(testFileNew));
		assertTrue(storage.fileExists(generateDirtyName(testFileNew)));
		
		fileData = storage.readFile(generateDirtyName(testFileNew));
		assertEquals(1, fileData.length);
		assertEquals(contentWrite, fileData[0]);
		
		// ABORT, ABORT!
		dm.abort();
		
		// Checks that nothing changed
		fileData = storage.readFile(testFileExists);
		assertEquals(1, fileData.length);
		assertEquals(testFileContent, fileData[0]);
		
		assertFalse(storage.fileExists(generateDirtyName(testFileExists)));
		assertFalse(storage.fileExists(generateDirtyName(testFileNew)));
		assertFalse(storage.fileExists(testFileNew));
	}
	
	@Test
	public void testCrashBeforePreCommit() throws IOException
	{
		dm.createFile(testFileNew);
		simulateCrash();
		
		// Now, DM was supposed to restore start and move to aborted
		assertEquals(TransactionStatus.Aborted, dm.getSatus());
		assertFalse(storage.fileExists(testFileNew));
		assertFalse(storage.fileExists(generateDirtyName(testFileNew)));
	}
	
	@Test
	public void testCrashAfterPreCommit() throws IOException
	{
		final String content = "Hello world!";

		// This also test multiple action handling.
		dm.createFile(testFileNew);
		dm.putContentsToFile(testFileNew, content);
		dm.preCommit();
		simulateCrash();
		
		// Now, DM was supposed to restore start and move to aborted
		assertEquals(TransactionStatus.PreCommit, dm.getSatus());
		assertFalse(storage.fileExists(testFileNew));
		assertTrue(storage.fileExists(generateDirtyName(testFileNew)));
		
		// we should be able to commit at this time
		dm.commit();
		
		// check what happened
		assertEquals(TransactionStatus.Comitted, dm.getSatus());
		assertTrue(storage.fileExists(testFileNew));
		assertFalse(storage.fileExists(generateDirtyName(testFileNew)));
		
		String fileData[] = storage.readFile(testFileNew);
		assertEquals(1, fileData.length);
		assertEquals(content, fileData[0]);
	}
	
	@Test
	public void testCrashInsideCommit() throws IOException
	{
		final String content = "Hello world!";

		// This also test multiple action handling.
		dm.createFile(testFileNew);
		dm.putContentsToFile(testFileNew, content);
		dm.preCommit();
		
		// HA ha ha, let's crash when we are trying to move changes to original file
		storage.crashFileName = testFileNew;
		boolean crashed = false;
		try {
			dm.commit();
		} catch (StorageCrash ex) {
			// Good - it is expected
			crashed = true;
		}
		
		assertTrue(crashed);
		
		// make sure that node can survive multiple crashes.
		crashed = false;
		try {
			simulateCrash();
		} catch (StorageCrash ex) {
			// Good - it is expected
			crashed = true;
		}
		
		// ok, two crashes is enough.
		storage.crashFileName = null;
		
		// Well, not realy, but it will clean storeage and stuff.
		simulateCrash();
		
		// Ok now the checks for the experience. Changes should have been commited.
		assertEquals(TransactionStatus.Comitted, dm.getSatus());
		assertTrue(storage.fileExists(testFileNew));
		assertFalse(storage.fileExists(generateDirtyName(testFileNew)));
		
		String fileData[] = storage.readFile(testFileNew);
		assertEquals(1, fileData.length);
		assertEquals(content, fileData[0]);
	}
}
